import type { UseFetchOptions } from '#app'
import type { FetchResponse, SearchParameters } from 'ofetch'
import { toast } from 'vue-sonner'

const SERVER = import.meta.server

type UrlType = string | Request | Ref<string | Request> | (() => string | Request)

export type HttpOption<T> = UseFetchOptions<Result<T>>

function handleError<T>(response: FetchResponse<Result<T>> & FetchResponse<ResponseType>) {
  const err = (text: string) => {
    // console.log(response?._data?.msg ?? text)
    // 消息提示
    toast(response?._data?.msg ?? text)
  }
  if (!response._data) {
    err('请求超时，服务器无响应！')
    return
  }
  // if (response._data.code === 402) {
  //   // token过期
  //   const userStore = useUserStore()
  //   userStore.clearUserInfo()
  //   err('登录状态已过期，需要重新登录')
  //   navigateTo('/login')
  //   return
  // }

  const handleMap: { [key: number]: () => void } = {
    404: () => err('服务器资源不存在'),
    500: () => err('服务器内部错误'),
    403: () => err('没有权限访问该资源'),
    401: () => {
      err('登录状态已过期，需要重新登录')
      // TODO 跳转实际登录页
      // navigateTo('/')
    },
  }
  if (handleMap[response.status]) {
    handleMap[response.status]()
  } else {
    err('未知错误！')
  }
}

function cloneDeep(entity: any, cache = new WeakMap()): any {
  const referenceTypes = ['Array', 'Object', 'Map', 'Set', 'Date']
  const entityType = Object.prototype.toString.call(entity)
  if (
    !new RegExp(referenceTypes.join('|')).test(entityType) ||
    entity instanceof WeakMap ||
    entity instanceof WeakSet
  ) {
    return entity
  }
  if (cache.has(entity)) {
    return cache.get(entity)
  }
  const c = new entity.constructor()

  if (entity instanceof Map) {
    entity.forEach((value, key) => c.set(cloneDeep(key), cloneDeep(value)))
  }
  if (entity instanceof Set) {
    entity.forEach(value => c.add(cloneDeep(value)))
  }
  if (entity instanceof Date) {
    return new Date(entity)
  }
  cache.set(entity, c)
  return Object.assign(
    c,
    ...Object.keys(entity).map(prop => {
      // debugger;
      return {
        [prop]: cloneDeep(entity[prop], cache),
      }
    })
  )
}

// get方法传递数组形式参数
function paramsSerializer(params?: SearchParameters) {
  if (!params) return

  const query = cloneDeep(params)
  const result: Record<string, any> = {}

  Object.entries(query).forEach(([key, val]) => {
    if (typeof val === 'object' && Array.isArray(val) && val !== null) {
      result[`${key}[]`] = toRaw(val).map((v: any) => JSON.stringify(v))
    } else {
      result[key] = val
    }
  })

  return result
}

async function fetch<T>(url: UrlType, option: any) {
  const runtimeConfig = useRuntimeConfig()
  const { apiBaseUrl, apiTimeout } = runtimeConfig.public

  return useFetch<Result<T>>(url, {
    // 请求拦截器
    onRequest({ options }) {
      // get方法传递数组形式参数
      options.params = paramsSerializer(options.params)
      options.timeout = apiTimeout
      // 添加baseURL,从环境变量里面取
      if (process.env.NODE_ENV === 'development') {
        // 解决跨域（会有两种情况，接口走服务端不用代理，但是走客户端需要代理，所以需要判断下）
        options.baseURL = SERVER ? apiBaseUrl : '/api'
      } else {
        options.baseURL = apiBaseUrl
      }
      options.headers = new Headers(options.headers)
      // 携带token
      // const userStore = useUserStore()
      // if (userStore.token) {
      //   options.headers.set('Authorization', `Bearer ${userStore.token}`)
      // }
    },
    // 响应拦截
    onResponse({ response }) {
      // 返回的是字符串，转化下
      if (response._data && typeof response._data == 'string') {
        response._data = JSON.parse(response._data)
      }
      if (response.headers.get('content-disposition') && response.status === 200) {
        return response
      }
      // 在这里判断错误 code没有统一返回规范
      // if (response._data.code !== '1') {
      //   handleError<T>(response)
      //   return Promise.reject(response._data)
      // }
      // 成功返回
      return response._data
    },
    // 错误处理
    async onResponseError({ response }) {
      handleError<T>(response)
      return Promise.reject(response?._data ?? null)
    },
    // 合并参数
    ...option,
  })
}

// 自动导出
export const useApi = {
  get: async <T>(url: UrlType, params?: any, option?: HttpOption<T>) => {
    return fetch<T>(url, { method: 'get', params, ...option })
  },

  post: async <T>(url: UrlType, body?: any, option?: HttpOption<T>) => {
    return fetch<T>(url, { method: 'post', body, ...option })
  },

  put: async <T>(url: UrlType, body?: any, option?: HttpOption<T>) => {
    return fetch<T>(url, { method: 'put', body, ...option })
  },

  delete: async <T>(url: UrlType, body?: any, option?: HttpOption<T>) => {
    return fetch<T>(url, { method: 'delete', body, ...option })
  },
}
